package org.elanfox.intellicommunity.biz.service.core.msg.impl;

import lombok.extern.slf4j.Slf4j;
import org.elanfox.intellicommunity.biz.dto.TrackDto;
import org.elanfox.intellicommunity.biz.dto.condition.MsgSearchCondition;
import org.elanfox.intellicommunity.biz.dto.msg.LocationMsgDTO;
import org.elanfox.intellicommunity.biz.dto.msg.MsgSaveDTO;
import org.elanfox.intellicommunity.biz.dto.msg.NodeMsgDTO;
import org.elanfox.intellicommunity.biz.dto.redis.RedisCardDTO;
import org.elanfox.intellicommunity.biz.dto.redis.RedisRoomDTO;
import org.elanfox.intellicommunity.biz.dto.redis.RedisUserDTO;
import org.elanfox.intellicommunity.biz.entity.embeddable.LocatorInfo;
import org.elanfox.intellicommunity.biz.entity.equipment.CardPO;
import org.elanfox.intellicommunity.biz.entity.msg.LocationMsgPO;
import org.elanfox.intellicommunity.biz.entity.msg.LocationMsgPO_;
import org.elanfox.intellicommunity.biz.entity.resident.StructurePO;
import org.elanfox.intellicommunity.biz.repository.msg.LocationMsgRepository;
import org.elanfox.intellicommunity.biz.service.core.equipment.CardService;
import org.elanfox.intellicommunity.biz.service.core.equipment.TrackService;
import org.elanfox.intellicommunity.biz.service.core.msg.LocationMsgService;
import org.elanfox.intellicommunity.biz.service.core.msg.NodeMsgService;
import org.elanfox.intellicommunity.biz.service.core.resident.StructureService;
import org.elanfox.intellicommunity.biz.vo.UserLocationVo;
import org.elanfox.intellicommunity.common.constant.RedisConstant;
import org.elanfox.intellicommunity.common.resource.StaticResourceService;
import org.elanfox.intellicommunity.common.utils.StringUtilsExt;
import org.elanfox.intellicommunity.data.base.base.SpecificationAndSort;
import org.elanfox.intellicommunity.data.base.dto.BaseSaveDTO;
import org.elanfox.intellicommunity.data.base.dto.BaseSearchDTO;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.domain.Page;
import org.springframework.data.domain.Sort;
import org.springframework.data.jpa.domain.Specification;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.scheduling.concurrent.ThreadPoolTaskExecutor;
import org.springframework.stereotype.Service;
import org.springframework.util.CollectionUtils;
import org.springframework.util.StringUtils;

import javax.annotation.Resource;
import javax.persistence.criteria.Predicate;
import java.io.IOException;
import java.net.URISyntaxException;
import java.time.LocalDateTime;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author allan
 * @version 1.0.0
 * @date 2019/12/20
 */
@Service
@Slf4j
public class LocationMsgServiceImpl extends AbstractMsgService<LocationMsgPO, Long> implements LocationMsgService {
    private LocationMsgRepository locationMsgRepository;
    @Autowired
    private RedisTemplate<String, Object> redisTemplate;
    @Autowired
    private NodeMsgService nodeMsgService;
    @Autowired
    private StaticResourceService resourceService;
    @Autowired
    private TrackService trackService;
    @Autowired
    private StructureService structureService;
    @Autowired
    private CardService cardService;

    @Resource(name = "msgThreadPool")
    private ThreadPoolTaskExecutor threadPoolTaskExecutor;


    @Autowired
    protected LocationMsgServiceImpl(LocationMsgRepository repository) {
        super(repository);
        this.locationMsgRepository = repository;
    }

    @Override
    public List<Object> findLastAll() {
        return redisTemplate.opsForHash().values(RedisConstant.KEY_LAST_LOCATION_MSG);
    }

    @Override
    public LocationMsgDTO findLastByUser(Integer userId) {
        //先取缓存
        LocationMsgDTO locationMsgDTO = (LocationMsgDTO) redisTemplate.opsForHash().get(RedisConstant.KEY_LAST_LOCATION_MSG, "user_" + userId);
        if (locationMsgDTO == null) {
            LocationMsgPO locationMsgPO = locationMsgRepository.findTopByUserIdOrderByCreateTimeDesc(userId);
            if (locationMsgPO == null) {
                return null;
            }
            locationMsgDTO = new LocationMsgDTO(locationMsgPO);
        }
        //判断定位消息是否有对应到房间
        if (!StringUtils.isEmpty(locationMsgDTO.getRoomName())) {
            RedisRoomDTO redisRoomDTO = (RedisRoomDTO) redisTemplate.opsForHash().get(RedisConstant.KEY_ROOM, "room_" + locationMsgDTO.getRoomName());
            if (redisRoomDTO != null && redisRoomDTO.getLocatorNo().equals(locationMsgDTO.getLocatorId())) {
                locationMsgDTO.setLatitude(redisRoomDTO.getLatitude());
                locationMsgDTO.setLongitude(redisRoomDTO.getLongitude());
            }
        }

        //拼接地址
        if (StringUtilsExt.isNotEmpty(locationMsgDTO.getImgUrl())) {
            try {
                locationMsgDTO.setImgUrl(resourceService.get(locationMsgDTO.getImgUrl()).getURI().toString());
            } catch (IOException e) {
                e.printStackTrace();
            } catch (URISyntaxException e) {
                e.printStackTrace();
            }
        }
        return locationMsgDTO;
    }

    @Override
    public Map<String, List> createTrackDto(Integer userId, LocalDateTime startTime, LocalDateTime endTime) {
        LocationMsgPO lastMsg = null;
        TrackDto lastTrack = null;

        List<TrackDto> list = new ArrayList<>();
        List<UserLocationVo> msgList = new ArrayList<>();
        MsgSearchCondition msgSearchCondition = new MsgSearchCondition();
        msgSearchCondition.setUserId(userId);
        msgSearchCondition.setStartTime(startTime);
        msgSearchCondition.setEndTime(endTime);
        msgSearchCondition.setSort(Sort.Direction.ASC);
        Page<LocationMsgPO> msgPOS = findAll(1, 100, msgSearchCondition);
        addTrackList(msgPOS.getContent(), list, lastMsg, lastTrack, msgList);
        for (int i = 2; i < msgPOS.getTotalPages(); i++) {
            msgPOS = findAll(i, 100, msgSearchCondition);
            addTrackList(msgPOS.getContent(), list, lastMsg, lastTrack, msgList);
        }
        Map<String, List> map = new HashMap<>();
        map.put("trackList", list);
        map.put("msgList", msgList);
        return map;
    }

    @Override
    public List<Object> findLastAllByMap(Integer structureId) {
        List<Object> list = new ArrayList<>();
        List<Object> allList = redisTemplate.opsForHash().values(RedisConstant.getKeyLocationStructure(structureId));
        List<Object> removeList = new ArrayList<>();
        for (Object obj : allList) {
            LocationMsgDTO po = (LocationMsgDTO) obj;
            if (!lo(po)) {
                removeList.add(obj);
            }
        }
        allList.removeAll(removeList);
        list.addAll(allList);
        //如果没有子节点
        List<StructurePO> strList = structureService.findChildren(structureId);
        if (CollectionUtils.isEmpty(strList)) {
            return list;
        }
        //如果有子节点
        for (StructurePO obj : strList) {
            child(list, obj.getStructureId());
        }
        return list;
    }

    private void child(List<Object> list, Integer structureId) {
        List<Object> allList = redisTemplate.opsForHash().values(RedisConstant.getKeyLocationStructure(structureId));
        List<Object> removeList = new ArrayList<>();
        for (Object obj : allList) {
            LocationMsgDTO po = (LocationMsgDTO) obj;
            if (!lo(po)) {
                removeList.add(obj);
            }
        }
        allList.removeAll(removeList);
        list.addAll(allList);
        List<StructurePO> strList = structureService.findChildren(structureId);
        if (CollectionUtils.isEmpty(strList)) {
            return;
        }
        for (StructurePO obj : strList) {
            child(list, obj.getStructureId());
        }
    }

    @Override
    public List<Object> findLastAllByStrId(Integer structureId) {
        List<Object> list = redisTemplate.opsForHash().values(RedisConstant.getKeyLocationStructure(structureId));
        List<Object> removeList = new ArrayList<>();
        for (Object obj : list) {
            LocationMsgDTO po = (LocationMsgDTO) obj;
            if (!lo(po)) {
                removeList.add(obj);
                continue;
            }
            if (!StringUtils.isEmpty(po.getRoomName())) {
                RedisRoomDTO roomPO = (RedisRoomDTO) redisTemplate.opsForHash().get(RedisConstant.KEY_ROOM, "room_" + po.getRoomName());
                if (roomPO != null && roomPO.getLocatorNo().equals(po.getLocatorId())) {
                    po.setLatitude(roomPO.getLatitude());
                    po.setLongitude(roomPO.getLongitude());
                }
            }
        }
        list.removeAll(removeList);
        return list;
    }

    /**
     * 过滤无效的消息
     *
     * @param dto
     * @return
     */
    private boolean lo(LocationMsgDTO dto) {
        RedisUserDTO userPO = (RedisUserDTO) redisTemplate.opsForHash().get(RedisConstant.KEY_USER, "user_" + dto.getUserId());
        if (userPO == null || !userPO.isDelFlag()) {
            return false;
        }
        RedisCardDTO cardPO = (RedisCardDTO) redisTemplate.opsForHash().get(RedisConstant.KEY_CARD, "card_" + dto.getCardId());
        if (cardPO == null || cardPO.isDeleteStatus() || !cardPO.isEnable()) {
            return false;
        }
        return true;
    }

    /**
     * 创建轨迹并将定位点添加到轨迹
     *
     * @param msgPOS
     * @param list
     */
    private void addTrackList(List<LocationMsgPO> msgPOS, List<TrackDto> list, LocationMsgPO lastMsg, TrackDto lastTrack, List<UserLocationVo> voList) {
        for (LocationMsgPO msgPO : msgPOS) {
            //创建Volist
            voList.add(new UserLocationVo(msgPO));
            //如果上一次消息是空的，直接创建一个新的TrackDto
            LocatorInfo locatorInfo = msgPO.getLocatorInfo();
            if (lastMsg == null) {
                TrackDto trackDto = createTrack(locatorInfo);
                list.add(trackDto);
                lastTrack = trackDto;
                lastMsg = msgPO;
                continue;
            }
            //如果上一次消息不为空，判断楼层是否一致,如果一致直接将节点摄入上一个轨迹
            if (locatorInfo.getStructureId() == lastMsg.getLocatorInfo().getStructureId()) {
                // TODO: 2020/1/8 判断两个节点之间是否有隐藏点
                List<Double[]> douList = trackService.find(lastMsg.getLocatorId(), msgPO.getLocatorId());
                if (!CollectionUtils.isEmpty(douList)) {
                    lastTrack.getPoints().addAll(douList);
                }
                Double[] point = {Double.parseDouble(locatorInfo.getLongitude()), Double.parseDouble(locatorInfo.getLatitude())};
                lastTrack.getPoints().add(point);
                continue;
            }
            //如果不一致
            TrackDto trackDto = createTrack(locatorInfo);
            list.add(trackDto);
            lastTrack = trackDto;
            lastMsg = msgPO;

        }
    }

    /**
     * 创建轨迹
     *
     * @param locatorInfo
     * @return
     */
    private TrackDto createTrack(LocatorInfo locatorInfo) {
        TrackDto trackDto = new TrackDto();
        //获取完整url
        try {
            trackDto.setImgUrl(resourceService.get(locatorInfo.getImgUrl()).getURI().toString());
        } catch (IOException e) {
            e.printStackTrace();
        } catch (URISyntaxException e) {
            e.printStackTrace();
        }
        trackDto.setStrName(locatorInfo.getAddress());
        trackDto.setStrId(locatorInfo.getStructureId());
        Double[] point = {Double.parseDouble(locatorInfo.getLongitude()), Double.parseDouble(locatorInfo.getLatitude())};
        trackDto.getPoints().add(point);
        return trackDto;
    }

    @Override
    protected SpecificationAndSort<LocationMsgPO> specificationAndSort(BaseSearchDTO searchDTO) {
        MsgSearchCondition condition = (MsgSearchCondition) searchDTO;
        Specification<LocationMsgPO> specification = (root, cq, cb) -> {
            List<Predicate> predicates = new ArrayList<>();
            predicates.add(normalPredicates(condition, root, cb));
            if (condition.getStartTime() != null && condition.getEndTime() != null) {
                predicates.add(cb.between(root.get(LocationMsgPO_.createTime), condition.getStartTime(), condition.getEndTime()));
            }
            return cb.and(predicates.toArray(new Predicate[predicates.size()]));
        };
        if (condition.getSort() == null) {
            condition.setSort(Sort.Direction.DESC);
        }
        Sort sort = new Sort(condition.getSort(), "createTime");
        return new SpecificationAndSort<>(specification, sort);
    }

    @Override
    public LocationMsgPO save(BaseSaveDTO saveDTO) {
        MsgSaveDTO msgSaveDTO = (MsgSaveDTO) saveDTO;
        CardPO cardPO = cardService.find(msgSaveDTO.getCardId());
        if (cardPO == null || cardPO.getUser() == null) {
            //用户未绑定，忽略
//            log.info(msgSaveDTO.toString() + "=====>未绑定用户信息");
            return null;
        }
        if (msgSaveDTO.getCardId() == 1676948976) {
            log.info("收到1676948976=>1111111111");
        }
        LocationMsgDTO lastMsg = this.findLastByUser(cardPO.getUser().getUserId());

        if (lastMsg != null && msgSaveDTO.getLocatorId() == (lastMsg.getLocatorId())) {
            //如果定位消息相同，则不录入
//            log.info("定位消息相同过滤，当前===>" + msgSaveDTO.getLocatorId() + "，上一条====>" + lastMsg.getLocatorId());
            if (msgSaveDTO.getCardId() == 1676948976) {
                log.info("收到1676948976=>2222222");
            }
            return null;
        }

//        if (lastMsg != null && msgSaveDTO.getDateTime().isBefore(lastMsg.createTime())) {
//            //时间在上一条之前的，忽略
//            return null;
//        }

        LocationMsgPO locationMsgPO = new LocationMsgPO();
        setMsgField(locationMsgPO, msgSaveDTO, cardPO);

        if (locationMsgPO.getLocatorInfo() == null) {
//            log.info("未找当相关定位器信息===编号===>" + msgSaveDTO.getLocatorId());
            if (msgSaveDTO.getCardId() == 1676948976) {
                log.info("收到1676948976=>33333333");
            }
            return null;
        }

        NodeMsgDTO nodeMsgDTO = nodeMsgService.findLastByUser(locationMsgPO.getUserId());
        //判断这次信息是否是节点消息，如果不是节点消息，判断是否在同一个楼层
        if (!locationMsgPO.getLocatorInfo().getFloorNode() && nodeMsgDTO != null &&
                !nodeMsgDTO.getStructureId().equals(locationMsgPO.getLocatorInfo().getStructureId())) {
            //不在同一个楼层的，忽略
            if (msgSaveDTO.getCardId() == 1676948976) {
                log.info("收到1676948976=>444444444");
            }
            return null;

        }


        if (lastMsg != null) {
            locationMsgPO.setLastMsgId(lastMsg.getId());
            //先把最新的设置为false
            locationMsgRepository.updateLatestTag(lastMsg.getId());
            //去掉缓存中的老数据
            redisTemplate.opsForHash().delete(RedisConstant.getKeyLocationStructure(lastMsg.getStructureId()), "user_" + lastMsg.getUserId());
        }
        //设置为最新的
        locationMsgPO.setLatest(true);
        locationMsgPO = repository.save(locationMsgPO);

        if (msgSaveDTO.getCardId() == 1676948976) {
            log.info("收到1676948976=>5555555");
        }

        //覆盖redis
        redisTemplate.opsForHash().put(RedisConstant.KEY_LAST_LOCATION_MSG, "user_" + locationMsgPO.getUserId(), new LocationMsgDTO(locationMsgPO));

        if (msgSaveDTO.getCardId() == 1676948976) {
            log.info("收到1676948976=>6666666");
        }

        redisTemplate.opsForHash().put(RedisConstant.getKeyLocationStructure(locationMsgPO.getLocatorInfo().getStructureId()), "user_" + locationMsgPO.getUserId(), new LocationMsgDTO(locationMsgPO));

        if (locationMsgPO.getLocatorInfo().getFloorNode()) {
            //异步存node表
            LocationMsgPO finalLocationMsgPO = locationMsgPO;
            threadPoolTaskExecutor.execute(() -> nodeMsgService.save(finalLocationMsgPO));
        }


        //管家需要特殊处理一下，已定位器为单位，将管家放到某个定位器的列表中，方便后期找到在这个定位器的管家
//        if (cardPO.getUser().getSteward()) {
//            redisTemplate.opsForHash().put(RedisConstant.KEY_STEWARD, "locator_" + msgSaveDTO.getLocatorId(), locationMsgPO.getUserId());
//        }

        return locationMsgPO;
    }

}
